/*
* Copyright (c) 2007 by Kirill Kolodyazhniy.
* See the file "license.terms" for information on usage and redistribution.
*/

#include "dxrenderwindow.h"
#include "dxrendersystem.h"
#include "dxmappings.h"
#include "graphic.h"

#include <algorithm>

#ifdef _DEBUG
#ifdef _MSC_VER
   #define new new( _CLIENT_BLOCK, __FILE__, __LINE__)
#endif
#endif // _DEBUG

namespace Engine
{
	/**********************************************************************************************************/
	DXRenderWindow::~DXRenderWindow()
	{
	}
	/**********************************************************************************************************/
	DXRenderWindow::DXRenderWindow(Direct3DGlobals* direct3d)
		:m_hWnd(NULL)
		,m_swapChain(NULL)
		,m_direct3d(direct3d)
		,m_isSwapChain(false)

	{
	}
	/**********************************************************************************************************/
	void DXRenderWindow::Create(const std::string& name, const PresentParams& pp)
	{
		m_name = name;
		if(!m_direct3d->d3dInterface.IsNull())
		{
			WNDCLASSEX wc;
			wc.cbSize = sizeof(WNDCLASSEX);

			HINSTANCE hInstance = GetModuleHandle(NULL);

			if(GetClassInfoEx(hInstance,"TPhysics",&wc) == FALSE)
			{
				wc.style =0;
				wc.lpfnWndProc = WindowProcedure;
				wc.cbClsExtra = 0;
				wc.cbWndExtra = 0;
				wc.hInstance = hInstance;
				wc.hIcon = NULL;
				wc.hCursor = LoadCursor(NULL,IDC_ARROW);
				wc.hbrBackground = CreateSolidBrush(GetSysColor (COLOR_BTNFACE));
				wc.lpszMenuName = NULL;
				wc.lpszClassName = "TPhysics";
				wc.hIconSm = NULL;

				//Register main window class
				if(!RegisterClassEx(&wc))
					throw std::logic_error("DXRenderWindow can't register win32 window class");
			}

			// Calculate window dimensions required
			// to get the requested client area
			DWORD dwStyle = WS_VISIBLE | WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SIZEBOX;
			RECT rc;
			SetRect(&rc, 0, 0, pp.width, pp.height);
			AdjustWindowRect(&rc, dwStyle, false);
			m_width = rc.right - rc.left;
			m_height = rc.bottom - rc.top;

			// Clamp width and height to the desktop dimensions
			int screenw = GetSystemMetrics(SM_CXSCREEN);
			int screenh = GetSystemMetrics(SM_CYSCREEN);
			if ((int)m_width > screenw)
			m_width = screenw;
			if ((int)m_height > screenh)
			m_height = screenh;


			int left = (screenw - m_width) / 2;
			int top = (screenh - m_height) / 2;


			m_hWnd = CreateWindow("TPhysics", "TPhysics", dwStyle, left, top, m_width, m_height,NULL,NULL, hInstance, this);
			if(!m_hWnd)
				throw std::logic_error("DXRenderWindow can't crate win32 window");

			DXMappings::Get(&m_d3dPresentParams,pp);
			m_d3dPresentParams.hDeviceWindow = m_hWnd;

			CreateD3DResources();
		}
		else
			throw std::invalid_argument("Zero Direct3D interface");
	}
	/**********************************************************************************************************/
	LRESULT CALLBACK DXRenderWindow::WindowProcedure (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
	{
		DXRenderWindow* pWnd = NULL;
		if(message == WM_NCCREATE)
			SetWindowLong(hWnd, GWL_USERDATA,reinterpret_cast<LONG>((LPCREATESTRUCT(lParam))->lpCreateParams));
		pWnd = reinterpret_cast<DXRenderWindow*>(GetWindowLong(hWnd, GWL_USERDATA));
		if(pWnd)
			return pWnd->OnMessage(hWnd, message, wParam, lParam);
		else
			return DefWindowProc(hWnd, message, wParam, lParam);
	}
	/**********************************************************************************************************/
	LRESULT DXRenderWindow::OnMessage (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
	{
		static bool mouseInWindow = false;
		switch(message)
		{
			case WM_MOUSELEAVE:
				if (mouseInWindow)
				{
					mouseInWindow = false;
					ShowCursor(true);
				}
				break;
			case WM_NCMOUSEMOVE:
				if (mouseInWindow)
				{
					mouseInWindow = false;
					ShowCursor(true);
				}
				break;
			case WM_MOUSEMOVE:
				if (!mouseInWindow)
				{
					mouseInWindow = true;
					ShowCursor(false);
				}
				break;
			case WM_ERASEBKGND:
				return 0;
			case WM_CLOSE:
				return 0;
			case WM_MOVE:
				if(m_d3dPresentParams.Windowed == TRUE)
					MovedOrResized();
				break;
			case WM_DISPLAYCHANGE:
				if(m_d3dPresentParams.Windowed == TRUE)
					MovedOrResized();
				break;
			case WM_SIZE:
				if(m_d3dPresentParams.Windowed == TRUE)
					MovedOrResized();
				break;
			case WM_GETMINMAXINFO:
				// Prevent the window from going smaller than some minimum size
				((MINMAXINFO*)lParam)->ptMinTrackSize.x = 100;
				((MINMAXINFO*)lParam)->ptMinTrackSize.y = 100;
				break;
		}
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	/**********************************************************************************************************/
	void DXRenderWindow::SwapBuffers()
	{
		if(!m_direct3d->d3dDevice.IsNull())
		{
			HRESULT hr;
			if (m_isSwapChain)
			{
				hr = m_swapChain->Present(NULL, NULL, NULL, NULL, 0);
			}
			else
			{
				hr = m_direct3d->d3dDevice->Present( NULL, NULL, NULL, NULL );
			}
			if( D3DERR_DEVICELOST == hr )
			{
				m_renderSurface.Delete();
				m_renderZBuffer.Delete();
				RenderSystemPtr rsptr = Graphic::GetSingleton().GetRenderSystem();
				static_cast<DXRenderSystem*>(rsptr.GetPointer())->NotifyDeviceLost();
			}
			else if( FAILED(hr) )
				throw std::logic_error("Error Presenting surfaces in D3D9RenderWindow::swapBuffers" );
		}

	}
	/**********************************************************************************************************/
	void DXRenderWindow::Accept(RenderTargetVisitor& visitor)
	{
		visitor.Visit(this);
	}
	/**********************************************************************************************************/
	LPDIRECT3DSURFACE9 DXRenderWindow::GetBackBuffer()
	{
		return m_renderSurface.GetPointer();
	}
	/**********************************************************************************************************/
	LPDIRECT3DSURFACE9 DXRenderWindow::GetZBuffer()
	{
		return m_renderZBuffer.GetPointer();
	}
	/**********************************************************************************************************/
	void DXRenderWindow::MovedOrResized()
	{
		if (!m_hWnd || IsIconic(m_hWnd))
			return;

		RECT rc;
		// width and height represent drawable area only
		GetClientRect(m_hWnd, &rc);
		unsigned int width = rc.right;
		unsigned int height = rc.bottom;
		if (m_width == width && m_height == height )
			return;

		m_renderSurface.Delete();
		m_renderZBuffer.Delete();

		if (m_isSwapChain)
		{

			D3DPRESENT_PARAMETERS pp = m_d3dPresentParams;

			pp.BackBufferWidth = width;
			pp.BackBufferHeight = height;

			m_swapChain.Delete();

			IDirect3DSwapChain9* swapChain = NULL;
			HRESULT hr = m_direct3d->d3dDevice->CreateAdditionalSwapChain(&pp,&swapChain);
			m_swapChain = ComPtr<IDirect3DSwapChain9>(swapChain);

			if (FAILED(hr))
			{
				throw std::logic_error("Can't reset swapchain to new size");
			}
			else
			{
				m_d3dPresentParams = pp;

				m_width = width;
				m_height = height;

				IDirect3DSurface9* renderSurface = NULL;
				hr = m_swapChain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &renderSurface);
				if (FAILED(hr))
					throw std::logic_error("Failed to back buffer for Swap Chain");
				m_renderSurface = ComPtr<IDirect3DSurface9>(renderSurface);

				UpdateZBuffer();

			}
			Log::GetSingleton() << "DX Swap chain reset" << std::endl;
		}
		// primary windows must reset the device
		else
		{
			m_d3dPresentParams.BackBufferWidth = m_width = width;
			m_d3dPresentParams.BackBufferHeight = m_height = height;
			RenderSystemPtr rsptr = Graphic::GetSingleton().GetRenderSystem();
			static_cast<DXRenderSystem*>(rsptr.GetPointer())->NotifyDeviceLost();
			Log::GetSingleton() << "DX device lost" << std::endl;
		}
	}
	/**********************************************************************************************************/
	void DXRenderWindow::Update(void)
	{
		RenderSystemPtr rsptr = Graphic::GetSingleton().GetRenderSystem();

		DXRenderSystem* rs = static_cast<DXRenderSystem*>(rsptr.GetPointer());

		if (rs->IsDeviceLost())
		{
			HRESULT hr = m_direct3d->d3dDevice->TestCooperativeLevel();
			if (hr == D3DERR_DEVICELOST)
			{
				m_renderSurface.Delete();
				Sleep(50);
				return;
			}
			else
			{
				// device lost, and we can reset
				rs->RestoreLostDevice();

				// Still lost?
				if (rs->IsDeviceLost())
				{
					// Wait a while
					Sleep(50);
					return;
				}

				if (!m_isSwapChain)
				{
					IDirect3DSurface9* renderSurface = NULL;
					m_direct3d->d3dDevice->GetRenderTarget( 0, &renderSurface );
					m_renderSurface = ComPtr<IDirect3DSurface9>(renderSurface);

					UpdateZBuffer();
				}
			}

		}
		RenderWindow::Update();
	}
	/**********************************************************************************************************/
	void DXRenderWindow::GetPresentationParameters(D3DPRESENT_PARAMETERS* params)
	{
		if(params)
		{
			*params = m_d3dPresentParams;
		}
	}
	/**********************************************************************************************************/
	void DXRenderWindow::CreateD3DResources()
	{
		HRESULT hr;
		if(m_direct3d->d3dDevice.IsNull())// If Direct3D device not created yet
		{
			//Get device capabilities
			ZeroMemory (&m_direct3d->d3dCaps, sizeof(D3DCAPS9));
			if(FAILED(m_direct3d->d3dInterface->GetDeviceCaps (D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &m_direct3d->d3dCaps)))
				throw std::logic_error("Can't get caps of Direct3D");

			//check fsaa
			RenderWindow::FSAAModes fsaa = GetAvailableFSAAModes();
			m_d3dPresentParams.MultiSampleQuality = 0;
			m_d3dPresentParams.MultiSampleType = static_cast<D3DMULTISAMPLE_TYPE>(fsaa.back());


			IDirect3DDevice9* d3dDevice = NULL;
			//Check if hardware vertex processing is available
			if (m_direct3d->d3dCaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT)
			{
				//Create device with hardware vertex processing
				hr = m_direct3d->d3dInterface->CreateDevice(D3DADAPTER_DEFAULT,D3DDEVTYPE_HAL, m_hWnd,
					D3DCREATE_HARDWARE_VERTEXPROCESSING|D3DCREATE_MULTITHREADED, &m_d3dPresentParams, &d3dDevice);
			}
			else
			{
				//Create device with software vertex processing
				hr = m_direct3d->d3dInterface->CreateDevice(D3DADAPTER_DEFAULT,D3DDEVTYPE_HAL, m_hWnd,
					D3DCREATE_SOFTWARE_VERTEXPROCESSING|D3DCREATE_MULTITHREADED, &m_d3dPresentParams, &d3dDevice);
			}
			if(FAILED(hr))
				throw std::logic_error("Can't create Direct3D device");

			m_direct3d->d3dDevice = ComPtr<IDirect3DDevice9>(d3dDevice);

			IDirect3DSurface9* renderSurface;
			m_direct3d->d3dDevice->GetRenderTarget( 0, &renderSurface );
			m_renderSurface = ComPtr<IDirect3DSurface9>(renderSurface);
		}
		else // If Direct3D device already created
		{
			IDirect3DSwapChain9* swapChain;
			hr = m_direct3d->d3dDevice->CreateAdditionalSwapChain(&m_d3dPresentParams, &swapChain);
			if (FAILED(hr))
			{
				// Try a second time, may fail the first time due to back buffer count,
				// which will be corrected by the runtime
				hr = m_direct3d->d3dDevice->CreateAdditionalSwapChain(&m_d3dPresentParams, &swapChain);
			}
			if (FAILED(hr))
			{
				throw std::logic_error("Unable to create an additional swap chain D3D9RenderWindow::createD3DResources");
			}
			m_swapChain = ComPtr<IDirect3DSwapChain9>(swapChain);
			m_isSwapChain = true;

			IDirect3DSurface9* renderSurface;
			m_swapChain->GetBackBuffer( 0, D3DBACKBUFFER_TYPE_MONO, &renderSurface );
			m_renderSurface = ComPtr<IDirect3DSurface9>(renderSurface);
		}
		UpdateZBuffer();
	}
	/**********************************************************************************************************/
	void DXRenderWindow::DestroyD3DResources()
	{
		if(m_isSwapChain)
			m_swapChain.Delete();

		m_renderZBuffer.Delete();
		m_renderSurface.Delete();

	}
	/**********************************************************************************************************/
	void DXRenderWindow::UpdateZBuffer()
	{
		HRESULT hr;
		m_renderZBuffer.Delete();
		IDirect3DSurface9* renderZBuffer = NULL;
		if(m_isSwapChain)
		{
			hr = m_direct3d->d3dDevice->CreateDepthStencilSurface(
							m_d3dPresentParams.BackBufferWidth, m_d3dPresentParams.BackBufferHeight,
							m_d3dPresentParams.AutoDepthStencilFormat,
							m_d3dPresentParams.MultiSampleType,
							m_d3dPresentParams.MultiSampleQuality,
							(m_d3dPresentParams.Flags & D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL),
							&renderZBuffer, NULL
							);
		}
		else
			hr = m_direct3d->d3dDevice->GetDepthStencilSurface(&renderZBuffer);

		if (FAILED(hr))
			throw std::logic_error("Unable to create a depth buffer for the swap chain");

		m_renderZBuffer = ComPtr<IDirect3DSurface9>(renderZBuffer);
	}
	/**********************************************************************************************************/
	RefPtr<RenderWindowInfo> DXRenderWindow::GetID()
	{
		return RefPtr<RenderWindowInfo>(new DXRenderWindowInfo(m_hWnd));
	}
	/**********************************************************************************************************/
	RenderWindow::Resolutions DXRenderWindow::GetAvailableResolutions() const
	{
		RenderWindow::Resolutions resList;
		for(UINT iMode=0; iMode < m_direct3d->d3dInterface->GetAdapterModeCount( D3DADAPTER_DEFAULT, m_d3dPresentParams.BackBufferFormat ); iMode++ )
		{
			D3DDISPLAYMODE displayMode;
			m_direct3d->d3dInterface->EnumAdapterModes( D3DADAPTER_DEFAULT, m_d3dPresentParams.BackBufferFormat, iMode, &displayMode );

			if(std::find(resList.begin(),resList.end(),RenderWindow::Resolutions::value_type(displayMode.Width,displayMode.Height)) == resList.end())
				resList.push_back(RenderWindow::Resolutions::value_type(displayMode.Width,displayMode.Height));
		}
		return resList;
	}
	/**********************************************************************************************************/
	void DXRenderWindow::SetResolution(unsigned int width, unsigned int height)
	{
		if(m_d3dPresentParams.BackBufferWidth != width || m_d3dPresentParams.BackBufferHeight != height)
		{
			if(m_d3dPresentParams.Windowed == TRUE)
			{
				DWORD dwStyle = GetWindowLong(m_hWnd,GWL_STYLE);
				RECT rc;
				SetRect(&rc, 0, 0, width, height);
				AdjustWindowRect(&rc, dwStyle, false);
				m_width = rc.right - rc.left;
				m_height = rc.bottom - rc.top;

				SetWindowPos(m_hWnd,HWND_TOPMOST,0,0,m_width,m_height,SWP_NOMOVE);
			}
			else
			{
				m_d3dPresentParams.BackBufferWidth = width;
				m_d3dPresentParams.BackBufferHeight = height;

				m_renderSurface.Delete();
				m_renderZBuffer.Delete();

				RenderSystemPtr rsptr = Graphic::GetSingleton().GetRenderSystem();
				static_cast<DXRenderSystem*>(rsptr.GetPointer())->NotifyDeviceLost();
			}
		}
	}
	/**********************************************************************************************************/
	RenderWindow::FSAAModes DXRenderWindow::GetAvailableFSAAModes() const
	{
		RenderWindow::FSAAModes aaList;
		aaList.push_back(0);

		for(UINT iMode = 2; iMode < 17; ++iMode)
		{
			if(SUCCEEDED(m_direct3d->d3dInterface->CheckDeviceMultiSampleType(D3DADAPTER_DEFAULT,m_direct3d->d3dCaps.DeviceType, m_d3dPresentParams.BackBufferFormat, m_d3dPresentParams.Windowed,static_cast<D3DMULTISAMPLE_TYPE>(iMode),NULL)))
				if(SUCCEEDED(m_direct3d->d3dInterface->CheckDeviceMultiSampleType(D3DADAPTER_DEFAULT,m_direct3d->d3dCaps.DeviceType, m_d3dPresentParams.AutoDepthStencilFormat, m_d3dPresentParams.Windowed,static_cast<D3DMULTISAMPLE_TYPE>(iMode),NULL)))
					aaList.push_back(iMode);
		}

		return aaList;
	}
	/**********************************************************************************************************/
	void DXRenderWindow::SetFSAAMode(unsigned int mode)
	{
		if(static_cast<D3DMULTISAMPLE_TYPE>(mode) != m_d3dPresentParams.MultiSampleType)
		{
			m_d3dPresentParams.MultiSampleType = static_cast<D3DMULTISAMPLE_TYPE>(mode);
			m_d3dPresentParams.MultiSampleQuality = 0;

			m_renderSurface.Delete();
			m_renderZBuffer.Delete();

			RenderSystemPtr rsptr = Graphic::GetSingleton().GetRenderSystem();
			static_cast<DXRenderSystem*>(rsptr.GetPointer())->NotifyDeviceLost();
		}
	}
	/**********************************************************************************************************/
	unsigned int DXRenderWindow::GetFSAAMode()
	{
		return m_d3dPresentParams.MultiSampleType;
	}
	/**********************************************************************************************************/
	unsigned int DXRenderWindow::GetBufferWidth()
	{
		return m_d3dPresentParams.BackBufferWidth;
	}
	/**********************************************************************************************************/
	unsigned int DXRenderWindow::GetBufferHeight()
	{
		return m_d3dPresentParams.BackBufferHeight;
	}
	/**********************************************************************************************************/
	void DXRenderWindow::SetFullScreen(bool val)
	{
		if((val ? FALSE : TRUE) != m_d3dPresentParams.Windowed)
		{
			m_d3dPresentParams.Windowed = (val ? FALSE : TRUE);
			if(m_d3dPresentParams.Windowed == TRUE)
			{
				DWORD dwStyle = WS_VISIBLE | WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SIZEBOX;
				SetWindowLong(m_hWnd, GWL_STYLE, dwStyle);
			}
			else
			{
				DWORD dwStyle = WS_VISIBLE | WS_CLIPCHILDREN;
				SetWindowLong(m_hWnd, GWL_STYLE, dwStyle);
			}
			SetWindowPos(m_hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_DRAWFRAME | SWP_FRAMECHANGED | SWP_NOSIZE| SWP_NOMOVE | SWP_NOACTIVATE);

			m_renderSurface.Delete();
			m_renderZBuffer.Delete();

			RenderSystemPtr rsptr = Graphic::GetSingleton().GetRenderSystem();
			static_cast<DXRenderSystem*>(rsptr.GetPointer())->NotifyDeviceLost();

			Log::GetSingleton() << "DX window toggle fullscreen" << std::endl;
		}
	}
	/**********************************************************************************************************/
	bool DXRenderWindow::IsFullScreen()
	{
		return m_d3dPresentParams.Windowed == FALSE;
	}
	/**********************************************************************************************************/
}
